package hxy.dragon.location_record.util.okhttps;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;

import com.ejlchina.data.Mapper;
import com.ejlchina.okhttps.Config;
import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.HttpResult;
import com.ejlchina.okhttps.HttpTask;
import com.ejlchina.okhttps.OnCallback;

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import hxy.dragon.location_record.MainActivity;

public class OkHttpsConfig implements Config {

    private String BASE_URL = "https://scan.cupb.top";
//    private String BASE_URL = "https://192.168.0.108:7070";
    private String TOKEN_REFRESH = "https://scan.cupb.top";

    // 绑定到主线程的 Handler
    private Handler mainHandler = new Handler(Looper.getMainLooper());
    private ProgressDialog loading = null;
    private AtomicInteger loadings = new AtomicInteger(0);

    @Override
    public void with(HTTP.Builder builder) {

        X509TrustManager myTrustManager = new X509TrustManager() {

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };

        HostnameVerifier myHostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        SSLContext sslCtx = null;
        try {
            sslCtx = SSLContext.getInstance("TLS");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        try {
            sslCtx.init(null, new TrustManager[]{myTrustManager}, new SecureRandom());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }

        SSLSocketFactory mySSLSocketFactory = sslCtx.getSocketFactory();

        builder.config(b -> {
            b.sslSocketFactory(mySSLSocketFactory, myTrustManager);
            b.hostnameVerifier(myHostnameVerifier);
        }).baseUrl(BASE_URL)          // 配置 BaseURL

                // 如果默认请求体是JSON，则开启，否则默认为表单
                //.bodyType("json")

                // 配置默认回调在主线程执行
                .callbackExecutor(run -> mainHandler.post(run))

                // 加载框预处理（在 TOKEN 处理之前）
                .addPreprocessor(chain -> {
                    HttpTask<?> task = chain.getTask();
                    // 根据标签判断是否显示加载框
                    if (task.isTagged(Tags.LOADING)) {
                        showLoading(context(task));
                    }
                    chain.proceed();
                })

                // 实现生命周期绑定
                .addPreprocessor(chain -> {
                    HttpTask<?> task = chain.getTask();
                    Object bound = task.getBound();
                    task.bind(new BoundWrapper(task, bound));
                    chain.proceed();
                })

                // TOKEN 处理（串行预处理）
                .addSerialPreprocessor(chain -> {
                    HttpTask<?> task = chain.getTask();
                    // 根据标签判断是否需要 Token
                    if (!task.isTagged(Tags.TOKEN)) {
                        chain.proceed();
                        return;
                    }
                    Context ctx = context(task);
                    requestTokenAndRefreshIfExpired(ctx, chain.getHttp(),
                            (String token) -> {
                                if (token != null) {
                                    // 添加 Token 头信息，名字需要和后端商定
                                    task.addHeader("Access-Token", token);
                                } else if (ctx != null) {
                                    // 若没有得到 Token, 则跳转登录页面
                                    ctx.startActivity(new Intent(ctx, MainActivity.class));
                                } else {
                                    Log.e("OkHttps", "没有 Context 无法跳转登录页面！");
                                }
                                // 无论如何，这行代码一定要执行到，不然后续接口会一直在排队中
                                chain.proceed();
                            });
                })

                // 错误码统一处理
                .responseListener((HttpTask<?> task, HttpResult result) -> {
                    // 刷新 Token 的接口可以例外
                    if (task.getUrl().contains(TOKEN_REFRESH)
                            || result.isSuccessful()) {
                        // 这里演示的是 HTTP 状态码的处理，如果有自定义的 code, 也可以进行深层次的判断
                        return true;            // 继续接口的业务处理
                    }
                    // 向用户展示接口的错误信息
                    showMsgToUser(task, result.getBody().toString());
                    return false;               // 阻断
                })

                // 生命周期绑定：第三步
                .completeListener((HttpTask<?> task, HttpResult.State state) -> {
                    Object bound = task.getBound();
                    if (bound instanceof BoundWrapper) {
                        ((BoundWrapper) bound).unbind();
                    }
                    // 网络错误统一处理
                    switch (state) {
                        case TIMEOUT:
                            hideLoading();
                            showMsgToUser(task, "网络连接超时");
                            break;
                        case NETWORK_ERROR:
                            hideLoading();
                            showMsgToUser(task, "网络错误，请检查WIFI或数据是否开启");
                            break;
                        case EXCEPTION:
                            hideLoading();
                            Log.e("kkk", state.toString());
                            showMsgToUser(task, "接口请求异常: " + task.getUrl());
                            break;
                    }
                    if (task.isTagged(Tags.LOADING)) {
                        hideLoading();          // 关闭加载框
                    }
                    return true;
                });
    }


    // 显示加载框
    private void showLoading(Context ctx) {

        if (loading == null) {
            // 这里就用 ProgressDialog 来演示了，当然可以替换成你喜爱的加载框
            loading = new ProgressDialog(ctx);
            loading.setMessage("正在加载，请稍等...");

        }
        // 增加加载框显示计数
        loadings.incrementAndGet();
        loading.show();
    }

    // 关闭加载框
    private void hideLoading() {
        // 判断是否所有显示加载框的接口都已完成
        if (loadings.decrementAndGet() <= 0
                && loading != null) {
            loading.dismiss();
            loading = null;
        }
    }


    static class BoundWrapper implements LifecycleObserver {

        HttpTask<?> task;
        Lifecycle lifecycle;
        Object bound;

        BoundWrapper(HttpTask<?> task, Object bound) {
            this.task = task;
            if (bound instanceof LifecycleOwner) {
                lifecycle = ((LifecycleOwner) bound).getLifecycle();
                lifecycle.addObserver(this);
            }
            this.bound = bound;
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        public void onStop() {
            task.cancel();  // 在 ON_STOP 事件中，取消对应的 HTTP 任务
        }

        void unbind() {
            if (lifecycle != null) {
                lifecycle.removeObserver(this);
            }
        }

    }

    /**
     * 获取 Context 对象
     **/
    private Context context(HttpTask<?> task) {
        Object bound = task.getBound();
        if (bound instanceof BoundWrapper) {
            bound = ((BoundWrapper) bound).bound;
        }
        if (bound instanceof Context) {
            return (Context) bound;
        }
        if (bound instanceof Fragment) {
            return ((Fragment) bound).getActivity();
        }
        // 还可以添加更多获取 Context 的策略，比如从全局 Application 里取
        return MainActivity.getAppContext();
    }

    /**
     * 向用户展示信息
     **/
    private void showMsgToUser(HttpTask<?> task, String message) {
        Context ctx = context(task);
        if (ctx != null) {
            // 这里就简单用 Toast 示例一下，有更高级的实现可以替换
            Looper.prepare();
            Toast.makeText(ctx, message, Toast.LENGTH_LONG).show();
            Looper.loop();
        }
    }

    /**
     * 获取TOKEN，若过期则刷新（代码中的字符串可以替换为常量）
     **/
    private void requestTokenAndRefreshIfExpired(Context ctx, HTTP http,
                                                 OnCallback<String> callback) {
        if (ctx == null) {
            callback.on(null);
            return;
        }
        // 这里演示使用 Preference 存储，也可以使用数据库存储
        SharedPreferences token = ctx.getSharedPreferences("token",
                Context.MODE_PRIVATE);
        long now = System.currentTimeMillis();
        // 刷新令牌
        String refreshToken = token.getString("refreshToken", null);
        // 判断有效期可以提前 60 秒，以防在接下来的网络延迟中过期了
        if (token.getLong("refreshTokenExpiresAt", 0) < now + 60000
                || refreshToken == null) {
            // 刷新令牌已过期，说明长时间未使用，需要重新登录
            callback.on(null);
            return;
        }
        // 访问令牌
        String accessToken = token.getString("accessToken", null);
        if (token.getLong("accessTokenExpiresAt", 0) > now + 60000
                && accessToken != null) {
            // 访问令牌未过期，则回调令牌
            callback.on(accessToken);
            return;
        }
        // 访问令牌已过期，刷新令牌未过期，则调接口刷新当前令牌
        http.async(TOKEN_REFRESH)
                .skipPreproc()      // 跳过所有预处理器
                .addBodyPara("refreshToken", refreshToken)
                .setOnResponse(res -> {
                    if (!res.isSuccessful()) {
                        callback.on(null);
                        return;
                    }
                    Mapper mapper = res.getBody().toMapper();
                    String newRefreshToken = mapper.getString("refreshToken");
                    String newAccessToken = mapper.getString("accessToken");
                    int refreshTokenExpiresIn = mapper.getInt("refreshTokenExpiresIn");
                    int accessTokenExpiresIn = mapper.getInt("accessTokenExpiresIn");
                    // 因为发生了请求，当前时间已经变化，所有重新获取时间
                    long now2 = System.currentTimeMillis();
                    // 保存到 SharedPreferences
                    token.edit()
                            .putString("refreshToken", newRefreshToken)
                            .putString("accessToken", newAccessToken)
                            .putLong("refreshTokenExpiresAt", refreshTokenExpiresIn * 1000 + now2)
                            .putLong("accessTokenExpiresAt", accessTokenExpiresIn * 1000 + now2)
                            .commit();
                    // 回调令牌
                    callback.on(newAccessToken);
                })
                .setOnException(e -> callback.on(null))
                .post();
    }

}